package cn.com.pc.content.aspect;

import cn.com.pc.aaa.Constants;
import cn.com.pc.aaa.model.Identity;
import cn.com.pc.content.annotation.LogRequired;
import cn.com.pc.content.domain.log.OperationLog;
import cn.com.pc.content.event.LogEvent;
import cn.com.pc.content.util.IpUtils;
import cn.com.pc.content.util.JsonUtils;
import cn.com.pc.content.util.SpelUtils;
import cn.com.pc.content.util.SpringCtxUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.bson.types.ObjectId;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

/**
 * API接口登录状态拦截器
 *
 * @author hjlu
 * @create 2021-03-11 18:20
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class LogRequiredAspect {

    private final HttpServletRequest request;

    /**
     * 定义拦截规则: 拦截Controller接口方法，即加了注解PostMapping、PutMapping、LogRequired的方法。
     * LogRequired配置日志细节
     */
    @Pointcut(
            "@annotation(cn.com.pc.content.annotation.LogRequired) " +
//                    "|| @annotation(org.springframework.web.bind.annotation.PutMapping) " +
//                    "|| @annotation(org.springframework.web.bind.annotation.PostMapping) " +
                    "")
    public void logPointcut() {
    }

    @Around("logPointcut()")
    public Object interceptor(ProceedingJoinPoint joinPoint) throws Throwable {

        // options直接放行，不记录日志。
        String requestMethod = request.getMethod();
        if ("options".equalsIgnoreCase(requestMethod)) {
            return joinPoint.proceed();
        }

        // 执行目标方法
        Object result = joinPoint.proceed();

        // 日志保存es
        OperationLog operationLog = getOperationLog(joinPoint, result);
        SpringCtxUtils.publishEvent(new LogEvent(operationLog, "createOperationLog", Map.of("id", operationLog.getId())));

        return result;
    }

    /**
     * 获取操作日志对象
     * @param joinPoint
     * @param result
     * @return
     */
    private OperationLog getOperationLog(ProceedingJoinPoint joinPoint, Object result) {
        Identity identity = (Identity) request.getAttribute(Constants.AAA_IDENTITY);
        long userId = identity != null ? identity.getId() : 0L;
        String userName = identity != null ? identity.getName() : "";

        OperationLog operationLog = new OperationLog();
        operationLog.setId(new ObjectId().toString());
        operationLog.setUserId(userId);
        operationLog.setUserName(userName);
        operationLog.setIp(IpUtils.getIp(request));
        operationLog.setLogAt(LocalDateTime.now());
        operationLog.setUri(request.getRequestURI());
        operationLog.setMethod(request.getMethod());
        operationLog.setParams(JsonUtils.obj2String(getParams(joinPoint)));
        operationLog.setOperation(getOperation(joinPoint, result));
        return operationLog;
    }

    /**
     * 获取请求参数
     * @param joinPoint
     * @return
     */
    private Map<String, Object> getParams(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取请求参数值数组
        Object[] args = joinPoint.getArgs();
        // 获取方法的所有请求参数名称的字符串数组
        String[] parameterNames = signature.getParameterNames();
        // 组装请求参数
        Map<String, Object> params = new HashMap<>();
        for (int i = 0; i < parameterNames.length; i++) {
            Object value;
            if (args[i] instanceof HttpServletRequest
                    || args[i] instanceof HttpServletResponse
                    || args[i] instanceof File
                    || args[i] instanceof MultipartFile) {
                value = null;
            } else {
                value = args[i];
            }
            params.put(parameterNames[i], value);
        }
        return params;
    }

    /**
     * 获取日志记录内容
     * @return
     */
    private String getOperation(ProceedingJoinPoint joinPoint, Object result) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LogRequired logRequired = method.getAnnotation(LogRequired.class);
        String operation = "";
        if (logRequired != null) {
            operation = logRequired.operation();
            // 获取日志SpEL表达式
            if (operation.contains("#")) {
                result = result == null ? "" : result;
                operation = SpelUtils.generateKeyBySpEL(operation, joinPoint, Map.of("_return", result));
            }
        }
        // 额外补充的日志记录内容
        String moreOperation = (String) request.getAttribute("operation");
        if (StringUtils.hasLength(moreOperation)) {
            operation = operation + moreOperation;
        }
        return operation;
    }

}
